home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 684 / 684.xpi / chrome / fireftp.jar / content / js / connection / sftp.js < prev    next >
Text File  |  2009-11-11  |  23KB  |  650 lines

  1. ftpMozilla.prototype.sftp = {
  2.   connect : function(reconnect) {
  3.     if (!reconnect) {                                                            // this is not a reconnection attempt
  4.       this.isReconnecting = false;
  5.       this.reconnectsLeft = parseInt(this.reconnectAttempts);
  6.  
  7.       if (!this.reconnectsLeft || this.reconnectsLeft < 1) {
  8.         this.reconnectsLeft = 1;
  9.       }
  10.     }
  11.  
  12.     if (!this.eventQueue.length || this.eventQueue[0].cmd != "welcome") {
  13.       this.unshiftEventQueue("welcome", "", "");                                 // wait for welcome message first
  14.     }
  15.  
  16.     ++this.networkTimeoutID;                                                     // just in case we have timeouts from previous connection
  17.     ++this.transferID;
  18.  
  19.     try {
  20.       var exec = this.getExec();
  21.  
  22.       if (!exec || !exec.exists()) {
  23.         this.onDisconnect();
  24.         return;
  25.       }
  26.  
  27.       this.ipcService    = Components.classes["@mozilla.org/process/ipc-service;1"].getService(Components.interfaces.nsIIPCService);
  28.       this.pipeTransport = Components.classes["@mozilla.org/process/pipe-transport;1"].createInstance(Components.interfaces.nsIPipeTransport);
  29.       this.ipcBuffer     = Components.classes["@mozilla.org/process/ipc-buffer;1"].createInstance(Components.interfaces.nsIIPCBuffer);
  30.  
  31.       this.ipcBuffer.open(65536, true);
  32.  
  33.       var command = exec.path.replace(/\x5c/g, "/");
  34.       var args    = [];
  35.  
  36.       if (this.password) {
  37.         args.push("-pw");
  38.         args.push(this.password);
  39.       }
  40.  
  41.       args.push("-P");
  42.       args.push(this.port);
  43.  
  44.       if (this.useCompression) {
  45.         args.push("-C");
  46.       }
  47.  
  48.       if (this.privatekey) {
  49.         args.push("-i");
  50.         args.push(this.privatekey.replace(/\x5c/g, "/"));
  51.       }
  52.  
  53.       args.push((this.login ? this.login + "@" : "") + this.host);
  54.  
  55.       this.pipeTransport.init(command, args, args.length, [], 0, 0, "", true, true, this.ipcBuffer);
  56.  
  57.       this.controlOutstream = this.pipeTransport.openOutputStream(0, 0, 0);
  58.  
  59.       var self = this;
  60.       var dataListener;
  61.       var func = function() {
  62.         if (!self.pipeTransport.isAttached()) {
  63.           self.onDisconnect();
  64.         }
  65.  
  66.         if (dataListener.data) {
  67.           if (dataListener.data.indexOf('\npsftp>') == -1 && dataListener.data.indexOf('\nStore key in cache') == -1
  68.            && dataListener.data.indexOf('\nUpdate cached key') == -1
  69.            && dataListener.data.indexOf('\nAccess denied') == -1
  70.            && dataListener.data.indexOf('Fatal: Network error:') == -1
  71.            && dataListener.data.indexOf('ssh_init:') != 0) {
  72.             return;
  73.           }
  74.  
  75.           var buf = dataListener.data;
  76.           dataListener.data = "";
  77.           self.readControl(buf);
  78.         }
  79.       };
  80.       this.readPoller = setInterval(func, 100);
  81.  
  82.       dataListener = {
  83.         data            : "",
  84.  
  85.         onStartRequest  : function(request, context) { },
  86.  
  87.         onStopRequest   : function(request, context, status) { },
  88.  
  89.         onDataAvailable : function(request, context, inputStream, offset, count) {
  90.           var controlInstream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
  91.           controlInstream.init(inputStream);
  92.           this.data += controlInstream.read(count);
  93.         }
  94.       };
  95.  
  96.       this.pipeTransport.asyncRead(dataListener, null, 0, 0, 0);
  97.  
  98.     } catch(ex) {
  99.       this.onDisconnect();
  100.     }
  101.   },
  102.  
  103.   keepAlive : function() {
  104.     // do nothing
  105.   },
  106.  
  107.   readControl : function(buffer) {
  108.     if (this.isKilling) {
  109.       return;
  110.     }
  111.  
  112.     try {
  113.       buffer = this.toUTF8.convertStringToUTF8(buffer, this.encoding, 1);
  114.     } catch (ex) {
  115.       if (this.observer) {
  116.         this.observer.onDebug(ex);
  117.       }
  118.     }
  119.  
  120.     if ((buffer == "2" && !this.isConnected) || buffer == "\r\n" || buffer == "\n") {
  121.       return;
  122.     }
  123.  
  124.     buffer         = buffer.replace(/\r\npsftp> /, '');
  125.     buffer         = buffer.replace(/\npsftp> /,   '');
  126.     var origBuffer = buffer;
  127.     buffer         = buffer.indexOf("\r\n") != -1 ? buffer.split("\r\n") : buffer.split("\n");
  128.     buffer         = buffer.filter(this.removeBlanks);
  129.  
  130.     if (origBuffer != "2" && origBuffer.indexOf('Store key in cache') == -1      // "2"s are self-generated fake messages
  131.      && origBuffer.indexOf('\nUpdate cached key') == -1
  132.      && origBuffer.indexOf('Fatal: Network error:') == -1
  133.      && origBuffer.indexOf('\nAccess denied') == -1
  134.      && origBuffer.indexOf('ssh_init:') != 0) {
  135.       for (var x = 0; x < buffer.length; ++x) {                                  // add response to log
  136.         var message   = buffer[x].charAt(buffer[x].length - 1) == '\r'
  137.                       ? buffer[x].substring(0, buffer[x].length - 1) : buffer[x];
  138.         if (this.observer) {
  139.           this.observer.onAppendLog(message, 'input', "info");
  140.         }
  141.  
  142.         if (message.indexOf('Listing directory') != -1) {
  143.           break;
  144.         }
  145.       }
  146.  
  147.       ++this.networkTimeoutID;
  148.     }
  149.  
  150.     var cmd;  var parameter;    var callback;   var callback2;
  151.  
  152.     if (this.eventQueue.length) {
  153.       cmd        = this.eventQueue[0].cmd;
  154.       parameter  = this.eventQueue[0].parameter;
  155.       callback   = this.eventQueue[0].callback;
  156.       callback2  = this.eventQueue[0].callback2;
  157.  
  158.       if (cmd != "ls" && cmd != "get" && cmd != "reget" && cmd != "put" && cmd != "reput") {   // used if we have a loss in connection
  159.         var throwAway = this.eventQueue.shift();
  160.  
  161.         if (throwAway.cmd != "welcome" && throwAway.cmd != "goodbye" && throwAway.cmd != "aborted" && throwAway.cmd != "sftpcache") {
  162.           this.trashQueue.push(throwAway);
  163.         }
  164.       }
  165.     } else {
  166.       cmd = "default";                                                           // an unexpected reply - perhaps a 421 timeout message
  167.     }
  168.  
  169.     switch (cmd) {
  170.       case "welcome":
  171.       case "sftpcache":
  172.         if (origBuffer.indexOf('Fatal: Network error:') != -1 || origBuffer.indexOf('ssh_init:') == 0) {
  173.           this.legitClose = true;
  174.           this.onDisconnect();
  175.           return;
  176.         }
  177.  
  178.         if (origBuffer.indexOf('Store key in cache') != -1 || origBuffer.indexOf('\nUpdate cached key') != -1) {
  179.           if (this.observer) {
  180.             var answer = this.observer.onSftpCache(origBuffer);
  181.  
  182.             if (answer) {
  183.               this.unshiftEventQueue("sftpcache", answer, "");
  184.             } else {
  185.               this.legitClose = true;
  186.               this.onDisconnect();
  187.               return;
  188.             }
  189.  
  190.             break;
  191.           }
  192.         }
  193.  
  194.         this.isConnected       = true;                                           // good to go
  195.  
  196.         if (this.observer) {
  197.           this.observer.onConnected();
  198.         }
  199.  
  200.         this.isReconnecting    = false;
  201.         this.reconnectsLeft    = parseInt(this.reconnectAttempts);               // setup reconnection settings
  202.  
  203.         if (!this.reconnectsLeft || this.reconnectsLeft < 1) {
  204.           this.reconnectsLeft = 1;
  205.         }
  206.  
  207.         var newConnectedHost = this.login + "@" + this.host;
  208.  
  209.         if (this.observer) {
  210.           this.observer.onLoginAccepted(newConnectedHost != this.connectedHost);
  211.         }
  212.  
  213.         if (newConnectedHost != this.connectedHost) {
  214.           this.legitClose = true;
  215.         }
  216.  
  217.         this.connectedHost = newConnectedHost;                                   // switching to a different host or different login
  218.  
  219.         if (!this.legitClose) {
  220.           this.recoverFromDisaster();                                            // recover from previous disaster
  221.           break;
  222.         }
  223.  
  224.         this.legitClose   = false;
  225.  
  226.         origBuffer = origBuffer.substring(origBuffer.indexOf("Remote working directory is") + 28);  // if buffer is not '/' we're chrooted
  227.         this.currentWorkingDir = origBuffer;
  228.  
  229.         if (this.observer) {
  230.           this.observer.onChangeDir(origBuffer != '/' && this.initialPath == '' ? origBuffer : '', false, origBuffer != '/' || this.initialPath != '');
  231.         }
  232.  
  233.         this.trashQueue = new Array();
  234.  
  235.         break;
  236.  
  237.       case "ls":
  238.       case "get":
  239.       case "reget":
  240.       case "put":
  241.       case "reput":
  242.         ++this.transferID;
  243.         this.eventQueue.shift();
  244.         if (this.eventQueue.length && this.eventQueue[0].cmd == "transferEnd") {
  245.           this.eventQueue.shift();
  246.         }
  247.         this.trashQueue = new Array();                                       // clear the trash array, completed an 'atomic' set of operations
  248.  
  249.         if (cmd == "ls") {
  250.           this.listData = this.parseListData(origBuffer, parameter);
  251.  
  252.           if (typeof callback == "string") {
  253.             eval(callback);                                                  // send off list data to whoever wanted it
  254.           } else {
  255.             callback();
  256.           }
  257.         }
  258.  
  259.         if (callback2) {                                                     // for transfers
  260.           if (typeof callback2 == "string") {
  261.             eval(callback2);
  262.           } else {
  263.             callback2();
  264.           }
  265.         }
  266.  
  267.         break;
  268.  
  269.       case "mkdir":
  270.       case "rm":
  271.       case "rmdir":
  272.         if (origBuffer.indexOf(': OK') != origBuffer.length - 4) {
  273.           if (this.observer) {
  274.             this.observer.onError(origBuffer + ": " + this.constructPath(this.currentWorkingDir, parameter));
  275.           }
  276.         } else {
  277.           if (cmd == "rmdir") {                                                  // clear out of cache if it's a remove directory
  278.             this.removeCacheEntry(this.constructPath(this.currentWorkingDir, parameter));
  279.           }
  280.  
  281.           if (typeof callback == "string") {
  282.             eval(callback);                                                      // send off list data to whoever wanted it
  283.           } else {
  284.             callback();
  285.           }
  286.         }
  287.  
  288.         this.trashQueue = new Array();
  289.         break;
  290.  
  291.       case "mv":
  292.         if (origBuffer.indexOf(': no such file or directory') != -1) {
  293.           if (this.observer) {
  294.             this.observer.onError(origBuffer + ": " + parameter);
  295.           }
  296.         } else {
  297.           if (typeof callback == "string") {
  298.             eval(callback);                                                      // send off list data to whoever wanted it
  299.           } else {
  300.             callback();
  301.           }
  302.         }
  303.  
  304.         this.trashQueue = new Array();
  305.         break;
  306.  
  307.       case "chmod":
  308.         if (typeof callback == "string") {
  309.           eval(callback);                                                        // send off list data to whoever wanted it
  310.         } else {
  311.           callback();
  312.         }
  313.  
  314.         this.trashQueue = new Array();
  315.         break;
  316.  
  317.       case "cd":
  318.         if (origBuffer.indexOf('Remote directory is now') == -1) {               // if it's not a directory
  319.           if (callback && typeof callback == "function") {
  320.             callback(false);
  321.           } else if (this.observer) {
  322.             this.observer.onDirNotFound(origBuffer);
  323.  
  324.             if (this.observer) {
  325.               this.observer.onError(origBuffer);
  326.             }
  327.           }
  328.         } else {
  329.           this.currentWorkingDir = parameter;
  330.  
  331.           if (this.observer) {                                                   // else navigate to the directory
  332.             this.observer.onChangeDir(parameter, typeof callback == "boolean" ? callback : "");
  333.           }
  334.  
  335.           if (callback && typeof callback == "function") {
  336.             callback(true);
  337.           }
  338.         }
  339.         break;
  340.  
  341.       case "aborted":
  342.       case "custom":
  343.         break;
  344.  
  345.       case "goodbye":                                                            // you say yes, i say no, you stay stop...
  346.       default:
  347.         if (origBuffer.indexOf('Access denied') != -1) {
  348.           if (this.observer && this.type == 'transfer') {
  349.             this.observer.onLoginDenied();
  350.           }
  351.  
  352.           this.cleanup();                                                        // login failed, cleanup variables
  353.  
  354.           if (this.observer && this.type != 'transfer' && this.type != 'bad') {
  355.             this.observer.onError(origBuffer);
  356.           }
  357.  
  358.           this.isConnected = false;
  359.  
  360.           this.kill();
  361.  
  362.           if (this.type == 'transfer') {
  363.             this.type = 'bad';
  364.           }
  365.  
  366.           if (this.observer && this.type != 'transfer' && this.type != 'bad') {
  367.             var self = this;
  368.             var func = function() { self.observer.onLoginDenied(); };
  369.             setTimeout(func, 0);
  370.           }
  371.  
  372.           return;
  373.         } else if (this.observer) {
  374.           this.observer.onError(origBuffer);
  375.         }
  376.         break;
  377.     }
  378.  
  379.     this.isReady = true;
  380.  
  381.     if (this.observer) {
  382.       this.observer.onIsReadyChange(true);
  383.     }
  384.  
  385.     if (this.eventQueue.length && this.eventQueue[0].cmd != "welcome") {         // start the next command
  386.       this.writeControl();
  387.     } else {
  388.       this.refresh();
  389.     }
  390.   },
  391.  
  392.   changeWorkingDirectory : function(path, callback) {
  393.     this.addEventQueue("cd", path, callback);
  394.     this.writeControlWrapper();
  395.   },
  396.  
  397.   makeDirectory : function(path, callback) {
  398.     this.addEventQueue("cd",    path.substring(0, path.lastIndexOf('/') ? path.lastIndexOf('/') : 1), true);
  399.     this.addEventQueue("mkdir", path.substring(path.lastIndexOf('/') + 1), callback);
  400.     this.writeControlWrapper();
  401.   },
  402.  
  403.   makeBlankFile : function(path, callback) {
  404.     this.addEventQueue("cd", path.substring(0, path.lastIndexOf('/') ? path.lastIndexOf('/') : 1), true);
  405.  
  406.     try {
  407.       var count = 0;
  408.       let tmpFile = Components.classes["@mozilla.org/file/directory_service;1"].createInstance(Components.interfaces.nsIProperties).get("TmpD", Components.interfaces.nsILocalFile);
  409.       tmpFile.append(count + '-blankFile');
  410.       while (tmpFile.exists()) {
  411.         ++count;
  412.         tmpFile.leafName = count + '-blankFile';
  413.       }
  414.       var foutstream = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
  415.       foutstream.init(tmpFile, 0x04 | 0x08 | 0x20, 0644, 0);
  416.       foutstream.write("", 0);
  417.       foutstream.close();
  418.  
  419.       this.upload(tmpFile.path, path, false, 0, 0, callback, true);
  420.     } catch (ex) {
  421.       if (this.observer) {
  422.         this.observer.onDebug(ex);
  423.       }
  424.     }
  425.   },
  426.  
  427.   remove : function(isDirectory, path, callback) {
  428.     if (isDirectory) {
  429.       this.unshiftEventQueue("rmdir", path.substring(path.lastIndexOf('/') + 1).replace(/\[/g, "\\[").replace(/\]/g, "\\]"), callback);
  430.       this.unshiftEventQueue("cd",    path.substring(0, path.lastIndexOf('/') ? path.lastIndexOf('/') : 1), true);
  431.  
  432.       var self         = this;
  433.       var listCallback = function() { self.removeRecursive(path); };
  434.       this.list(path, listCallback, true, true);
  435.     } else {
  436.       this.unshiftEventQueue("rm",    path.substring(path.lastIndexOf('/') + 1).replace(/\[/g, "\\[").replace(/\]/g, "\\]"), callback);
  437.       this.unshiftEventQueue("cd",    path.substring(0, path.lastIndexOf('/') ? path.lastIndexOf('/') : 1), true);
  438.     }
  439.  
  440.     this.writeControlWrapper();
  441.   },
  442.  
  443.   removeRecursive : function(parent) {                                           // delete subdirectories and files
  444.     var files = this.listData;
  445.  
  446.     for (var x = 0; x < files.length; ++x) {
  447.       var remotePath = this.constructPath(parent, files[x].leafName);
  448.  
  449.       if (files[x].isDirectory()) {                                              // delete a subdirectory recursively
  450.         this.unshiftEventQueue("rmdir",  remotePath.substring(remotePath.lastIndexOf('/') + 1).replace(/\[/g, "\\[").replace(/\]/g, "\\]"), "");
  451.         this.unshiftEventQueue("cd",     parent, true);
  452.         this.removeRecursiveHelper(remotePath);
  453.       } else {                                                                   // delete a file
  454.         this.unshiftEventQueue("rm",     remotePath.substring(remotePath.lastIndexOf('/') + 1).replace(/\[/g, "\\[").replace(/\]/g, "\\]"), "");
  455.         this.unshiftEventQueue("cd",     parent, true);
  456.       }
  457.     }
  458.   },
  459.  
  460.   removeRecursiveHelper : function(remotePath) {
  461.     var self           = this;
  462.     var listCallback   = function() { self.removeRecursive(remotePath); };
  463.     this.list(remotePath, listCallback, true, true);
  464.   },
  465.  
  466.   rename : function(oldName, newName, callback, isDir) {
  467.     if (isDir) {
  468.       this.removeCacheEntry(oldName);
  469.     }
  470.  
  471.     oldName = oldName.replace(/\[/g, "\\[").replace(/\]/g, "\\]");
  472.  
  473.     this.addEventQueue("mv", '"' + this.escapeSftp(oldName) + '" "' + this.escapeSftp(newName) + '"', callback);                 // rename the file
  474.     this.writeControlWrapper();
  475.   },
  476.  
  477.   changePermissions : function(permissions, path, callback) {
  478.     path = path.replace(/\[/g, "\\[").replace(/\]/g, "\\]");
  479.  
  480.     this.addEventQueue("cd",    path.substring(0, path.lastIndexOf('/') ? path.lastIndexOf('/') : 1), true);
  481.     this.addEventQueue("chmod", permissions + ' "' + this.escapeSftp(path.substring(path.lastIndexOf('/') + 1)) + '"', callback);
  482.     this.writeControlWrapper();
  483.   },
  484.  
  485.   custom : function(cmd) {
  486.     this.addEventQueue("custom", cmd);
  487.     this.writeControlWrapper();
  488.   },
  489.  
  490.   list : function(path, callback, skipCache, recursive, fxp) {
  491.     if (!skipCache && this.sessionsMode) {
  492.       if (this.cacheHit(path, callback)) {
  493.         return;
  494.       }
  495.     }
  496.  
  497.     if (recursive) {
  498.       this.unshiftEventQueue(  "ls", path, callback, '');
  499.       this.unshiftEventQueue(  "cd", path, "",       '');
  500.     } else {
  501.       this.addEventQueue(      "cd", path, "",       '');
  502.       this.addEventQueue(      "ls", path, callback, '');
  503.     }
  504.  
  505.     this.writeControlWrapper();
  506.   },
  507.  
  508.   download : function(remotePath, localPath, remoteSize, resume, localSize, isSymlink, callback) {
  509.     ++this.queueID;
  510.     var id = this.connNo + "-" + this.queueID;
  511.  
  512.     this.addEventQueue("transferBegin", "", { id: id });
  513.  
  514.     this.addEventQueue(  "cd",  remotePath.substring(0, remotePath.lastIndexOf('/') ? remotePath.lastIndexOf('/') : 1), true);
  515.  
  516.     var leafName = remotePath.substring(remotePath.lastIndexOf('/') + 1);
  517.  
  518.     this.addEventQueue(resume ? "reget" : "get", '"' + this.escapeSftp(leafName) + '" "' + this.escapeSftp(localPath.replace(/\x5c/g, "/")) + '"', localPath, callback);
  519.  
  520.     this.addEventQueue("transferEnd", "", { localPath: localPath, remotePath: remotePath, size: remoteSize, transport: 'sftp', type: 'download', ascii: "I", id: id });
  521.  
  522.     this.writeControlWrapper();
  523.   },
  524.  
  525.   upload : function(localPath, remotePath, resume, localSize, remoteSize, callback, disableMDTM) {
  526.     ++this.queueID;
  527.     var id = this.connNo + "-" + this.queueID;
  528.  
  529.     this.addEventQueue("transferBegin", "", { id: id });
  530.  
  531.     this.addEventQueue(  "cd",  remotePath.substring(0, remotePath.lastIndexOf('/') ? remotePath.lastIndexOf('/') : 1), true);
  532.  
  533.     var leafName = remotePath.substring(remotePath.lastIndexOf('/') + 1);
  534.  
  535.     this.addEventQueue(resume ? "reput" : "put", '"' + this.escapeSftp(localPath.replace(/\x5c/g, "/")) + '" "' + this.escapeSftp(leafName) + '"', localPath, callback);
  536.  
  537.     this.addEventQueue("transferEnd", "", { localPath: localPath, remotePath: remotePath, size: localSize, transport: 'sftp', type: 'upload', ascii: "I", id: id });
  538.  
  539.     this.writeControlWrapper();
  540.  
  541.     return id;
  542.   },
  543.  
  544.   isListing : function() {                                                       // check queue to see if we're listing
  545.     for (var x = 0; x < this.eventQueue.length; ++x) {
  546.       if (this.eventQueue[x].cmd.indexOf("ls") != -1) {
  547.         return true;
  548.       }
  549.     }
  550.  
  551.     return false;
  552.   },
  553.  
  554.   recoverFromDisaster : function() {                                             // after connection lost, try to restart queue
  555.     if (this.eventQueue.length && this.eventQueue[0].cmd == "goodbye") {
  556.       this.eventQueue.shift();
  557.     }
  558.  
  559.     if (this.eventQueue.cmd) {
  560.       this.eventQueue = new Array(this.eventQueue);
  561.     }
  562.  
  563.     if (this.eventQueue.length && (this.eventQueue[0].cmd == "ls"
  564.                                ||  this.eventQueue[0].cmd == "get"
  565.                                ||  this.eventQueue[0].cmd == "reget"
  566.                                ||  this.eventQueue[0].cmd == "put"
  567.                                ||  this.eventQueue[0].cmd == "reput")) {
  568.       var cmd       = this.eventQueue[0].cmd;
  569.       var parameter = this.eventQueue[0].parameter;
  570.  
  571.       cmd = this.eventQueue[0].cmd;
  572.  
  573.       if (cmd == "put") {                                                        // set up resuming for these poor interrupted transfers
  574.         this.eventQueue[0].cmd = "reput";
  575.       } else if (cmd == "get") {
  576.         this.eventQueue[0].cmd = "reget";
  577.       }
  578.     }
  579.  
  580.     if (this.currentWorkingDir) {
  581.       this.unshiftEventQueue("cd", this.currentWorkingDir, true);
  582.       this.currentWorkingDir = "";
  583.     }
  584.  
  585.     this.trashQueue = new Array();
  586.   },
  587.  
  588.   getExec : function() {
  589.     if (this.getPlatform() == "windows") {
  590.       var exec = Components.classes["@mozilla.org/file/directory_service;1"].createInstance(Components.interfaces.nsIProperties)
  591.                            .get("ProfD", Components.interfaces.nsILocalFile);
  592.       exec.append("extensions");
  593.       exec.append("{a7c6cf7f-112c-4500-a7ea-39801a327e5f}");
  594.       exec.append("platform");
  595.       exec.append("WINNT_x86-msvc");
  596.       exec.append("psftp.exe");
  597.  
  598.       return exec;
  599.     } else if (this.getPlatform() == "linux") {
  600.       var file = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile);
  601.       file.initWithPath("/usr/bin/psftp");
  602.  
  603.       return file;
  604.     } else if (this.getPlatform() == "mac") {
  605.       var file = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile);
  606.       file.initWithPath("/opt/local/var/macports/software/putty");
  607.  
  608.       if (!file.exists()) {
  609.         return file;
  610.       }
  611.  
  612.       var subdirs = [];
  613.  
  614.       var entries = file.directoryEntries;                                       // find highest version number
  615.       while (entries.hasMoreElements()) {
  616.         subdirs.push(entries.getNext().QueryInterface(Components.interfaces.nsILocalFile));
  617.       }
  618.  
  619.       subdirs.sort(compareName);
  620.       subdirs.reverse();
  621.  
  622.       if (!subdirs.length) {
  623.         return file;
  624.       }
  625.  
  626.       file.append(subdirs[0].leafName);
  627.       file.append("opt");
  628.       file.append("local");
  629.       file.append("bin");
  630.       file.append("psftp");
  631.  
  632.       return file;
  633.     }
  634.   },
  635.  
  636.   getPlatform : function() {
  637.     var platform = navigator.platform.toLowerCase();
  638.  
  639.     if (platform.indexOf('linux') != -1) {
  640.       return 'linux';
  641.     }
  642.  
  643.     if (platform.indexOf('mac') != -1) {
  644.       return 'mac';
  645.     }
  646.  
  647.     return 'windows';
  648.   }
  649. };
  650.